package img

import (
	"fmt"
	"image"
	"image/draw"
	"image/gif"
	"image/jpeg"
	"image/png"
	"log"
	"os"
	"path/filepath"

	"github.com/nfnt/resize"
	"github.com/nxshock/colorcrop"
)

const maxSide float32 = 600

type Img struct {
	Link      string
	FullPath  string
	Extension string
	Logger    *log.Logger
}

func NewImg(link, storepath, name string, orderNum int, logger *log.Logger) Img {
	localName := name + fmt.Sprintf("-%02d", orderNum)
	return Img{
		Link:     link,
		FullPath: filepath.Join(storepath, localName),
		Logger:   logger,
	}
}

func (im *Img) writeImage(data image.Image) (err error) {
	out, err := os.OpenFile(im.FullPath, os.O_WRONLY, os.ModeAppend)
	if err != nil {
		panic(fmt.Errorf("[writeImage]> path: %v:\n%v\n", im.FullPath, err))
	}
	defer out.Close()

	im.Logger.Printf("(%v) Запись в %v", im.Extension, im.FullPath)
	switch im.Extension {
	case "jpeg":
		err = jpeg.Encode(out, data, &jpeg.Options{Quality: 100})
	case "png":
		err = png.Encode(out, data)
	case "gif":
		err = gif.Encode(out, data, &gif.Options{NumColors: 256})
	}

	if err != nil {
		im.Logger.Printf("Ошибка при записи файла:\n%v\n", err)
	} else {
		im.Logger.Println("Файл успешно записан")
	}

	return
}

func (im *Img) readImage() (data image.Image, err error) {
	in, err := os.OpenFile(im.FullPath, os.O_RDONLY, os.ModeAppend)
	defer in.Close()

	switch im.Extension {
	case "jpeg":
		data, err = jpeg.Decode(in)
	case "png":
		data, err = png.Decode(in)
	case "gif":
		data, err = gif.Decode(in)
	}

	if err != nil {
		im.Logger.Printf("Ошибка при чтении файла:\n%v\n", err)
	} else {
		im.Logger.Println("Файл успешно прочитан")
	}

	return
}

func (im *Img) generateMask() (mask image.Image, err error) {
	data, err := im.readImage()
	if err != nil {
		return
	}
	interMask := image.NewNRGBA(data.Bounds())

	// It's nned for "converting" from other color models
	bufferPic := image.NewNRGBA(data.Bounds())
	draw.Draw(bufferPic, bufferPic.Bounds(), data, bufferPic.Bounds().Min, draw.Src)
	pixels := bufferPic.Pix

	for i := range pixels {
		if (i+1)%4 == 0 {
			interMask.Pix[i-3] = pixels[i]
			interMask.Pix[i-2] = pixels[i]
			interMask.Pix[i-1] = pixels[i]
			interMask.Pix[i] = 255
		}
	}
	mask = interMask
	im.Logger.Println("Маска создана")
	return
}

func (im *Img) fillBackground() (err error) {
	data, err := im.readImage()
	if err != nil {
		panic(err)
	}
	background := image.NewRGBA(data.Bounds())
	mask, err := im.generateMask()
	if err != nil {
		return
	}

	for i := range background.Pix {
		background.Pix[i] = uint8(255)
	}

	draw.DrawMask(background, data.Bounds(), data, data.Bounds().Min, mask, mask.Bounds().Min, draw.Over)
	im.Logger.Println("Задник залит белым цветом")

	err = im.writeImage(background)
	if err != nil {
		panic(err)
	}

	return
}

func (im *Img) ConvertToJPEG(override bool) (err error) {
	if im.Extension == "jpeg" {
		err = fmt.Errorf("[convert]> Изображение уже в формате jpeg")
		im.Logger.Println(err)
		return
	}

	newName := im.FullPath[:len(im.FullPath)-4] + ".jpeg"
	if _, err = os.Stat(newName); err == nil && !override {
		err = fmt.Errorf("Файл '%s' существует, но параметр 'override' запрещает его перезаписывать\n", newName)
		im.Logger.Print(err)
		return
	}

	im.fillBackground()
	data, err := im.readImage()
	if err != nil {
		panic(err)
	}

	err = os.Rename(im.FullPath, newName)
	if err != nil {
		im.Logger.Printf("[convert]> Ошибка при переименовании файла:\n%v\n", err)
		return
	}
	(*im).FullPath = newName
	(*im).Extension = "jpeg"
	im.Logger.Printf("[convert]> Новый полный путь до файла:\n%v\n", im.FullPath)

	err = im.writeImage(data)
	if err != nil {
		panic(err)
	}

	im.Logger.Println("[convert]> Файл сконвертирован в jpeg")
	return
}

func (im *Img) TrimImage() (err error) {
	data, err := im.readImage()
	if err != nil {
		im.Logger.Printf("[trim]> Ну, я тут точно ошибся:\n%v\n", err)
	}

	base := image.Transparent
	if im.Extension == "jpeg" {
		base = image.White
	}

	cropped := colorcrop.Crop(data, base, 0.1)

	err = im.writeImage(cropped)
	if err != nil {
		panic(err)
	}
	im.Logger.Println("[trim]> Пустые области картинки успешно обрезаны")

	return
}

func (im *Img) Scale() (err error) {
	data, err := im.readImage()
	if err != nil {
		panic(err)
	}
	fWidth := float32(data.Bounds().Dx())
	fHeight := float32(data.Bounds().Dy())

	if fHeight > fWidth {
		fWidth /= (fHeight / maxSide)
		fHeight = maxSide
	} else if fHeight < fWidth {
		fHeight /= (fWidth / maxSide)
		fWidth = maxSide
	} else {
		fHeight = maxSide
		fWidth = maxSide
	}

	data = resize.Resize(uint(fWidth), uint(fHeight), data, resize.Bilinear)

	err = im.writeImage(data)
	if err != nil {
		err = fmt.Errorf("[scale]> Не удалось конвертировать картинку:\n%v\n", err)
		im.Logger.Println(err)
		return
	}
	im.Logger.Println("[scale]> Изображение масштабировано")

	return
}

func (im *Img) MakeSquare() (err error) {
	data, err := im.readImage()
	if err != nil {
		panic(err)
	}
	x0 := data.Bounds().Min.X
	y0 := data.Bounds().Min.Y

	x1 := data.Bounds().Max.X
	y1 := data.Bounds().Max.Y

	xSide := data.Bounds().Dx()
	ySide := data.Bounds().Dy()

	if xSide > ySide {
		middleOfLowerSide := y1 - ySide/2
		y0 = middleOfLowerSide - xSide/2
		y1 = middleOfLowerSide + xSide/2
	} else if xSide < ySide {
		middleOfLowerSide := x1 - xSide/2
		x0 = middleOfLowerSide - ySide/2
		x1 = middleOfLowerSide + ySide/2
	} else {
		err = fmt.Errorf("[square]> Картинка уже квадратная: %dx%d\n", xSide, ySide)
		im.Logger.Print(err)
		return
	}

	squareData := image.NewNRGBA(image.Rect(0, 0, x1-x0, y1-y0))

	for i := range squareData.Pix {
		squareData.Pix[i] = 255
	}

	draw.Draw(squareData, squareData.Bounds(), data, image.Point{X: x0, Y: y0}, draw.Src)
	err = im.writeImage(squareData)
	if err != nil {
		panic(err)
	}

	im.Logger.Println("[square]> Контент изображения отцентрован, длина и ширина картинки теперь равны")
	return
}
